package com.lagou.edu.factory;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @className: BeanFactory
 * @Description: TODO
 * @author: Huangjianyun
 * @date: 2022/3/21 18:14
 * 工厂类： 使用反射技术生产对象
 */
public class BeanFactory {
    /*
    * 需要做两件事
    * 1读取xml文件全路径，通过反射技术实例化对象 存储待用（map集合存储）
    * 2给外部提供 获取实例化对象 接口方法（根据id获取）
    *
    * */
    //1声明一个map集合 用于存储实例化对象
    private static Map<String,Object> map = new HashMap<>();
    //2项目一启动立即读取解析xml配置，获取类全路径 使用静态代码块
    static {
        //执行读取xml文件全路径，通过反射技术实例化对象
        //2.1加载xml配置文件
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        //2.2解析xml 需要在pom.xml引入的dom4j依赖 xpath表达式依赖（快速定位xml的元素）
        //创建SAXReader对象，
        SAXReader saxReader = new SAXReader();
        //读取xml配置文件 返回xml文档对象Document
        try {
            Document document = saxReader.read(resourceAsStream);
            //查找document文档对象里面的bean标签元素， 先获取根元素
            Element rootElement = document.getRootElement();
            //根元素，选择根下面节点 传入xpath表达式 获取跟标签下所有的bean元素
            /*
            *  经常使用的xpath的用法
            *   nodename: 选择此节点所有子节点
            *   /    从根节点选取  即选择根元素子元素
            *   //   从匹配的选择当前节点的选择文档的中的节点，而不考虑它们位置（有些元素含有层级关系 ，这里不考虑，只要匹配元素就进行选择）
            *   .    选择当前节点
            *   ..   选择当前节点的父节点
            *   @    选择属性
            * */
            List<Element> beanList = rootElement.selectNodes("//bean");
            //遍历处理每一个bean元素： 获取改bean元素id  和  class属性
            for (int i = 0; i < beanList.size() ; i++) {
                //获取bean元素
                Element element = beanList.get(i);
                //获取bean元素的 id和class属性值 比如
                String id = element.attributeValue("id");  //accountDao
                String clazz = element.attributeValue("class"); //com.lagou.edu.dao.impl.JdbcAccountDaoImpl
                // 通过反射技术进行实例化对象
                Class<?> aClass = Class.forName(clazz);
                //使用类路径对应 Class对象实例化
                Object o = aClass.newInstance(); //实例化对象
                //实例化的对象进行存储在map容器中
                map.put(id,o);
            }
            //实例化完成之后， 检查哪些对象想要进行传值进入， 根据它的配置，传入那些值
            //xpath表达式 找到property元素的bean就有传值的需求
            List<Element> propertyList = rootElement.selectNodes("//property");
            //解析property获取其父元素
            for (int i = 0; i < propertyList.size(); i++) {
                //<property name="AccountDao" ref="accountDao"></property> name找到set方法，  ref为setter()传入具体值
                Element element = propertyList.get(i);
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");
                //找到当前需要被处理依赖关系的bean的（即property元素父元素）
                Element parent = element.getParent();
                //调用父元素得反射功能
                String parentId = parent.attributeValue("id");
                Object parentObject = map.get(parentId);  //property得父元素bean实例化对象
                //遍历 父对象的所有方法， 找到 "set"+"name(property标签name属性值)"
                Method[] methods = parentObject.getClass().getMethods();
                for (int j = 0; j < methods.length; j++) {
                    Method method = methods[j];
                    //判断  找到set方法
                    if (method.getName().equalsIgnoreCase("set"+name)){ //当前是 public void setAccountDao(AccountDao accountDao)
                        //通过反射技术 method 调用parentObect父对象方法， 把注入即ref 引用bean得对象值塞进set方法完成注入值过程
                        method.invoke(parentObject,map.get(ref));
                    }
                }
                // 把处理完成之后 parentObject重新放到map中  id = transferService
                map.put(parentId,parentObject);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    //任务二：对外提供实例对象接口（根据id进行获取）
    public static Object getBean(String id){
        return map.get(id);
    }
}
